package com.swak.persistence;

import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.session.RowBounds;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.Assert;

import com.swak.annotation.Table;
import com.swak.define.CurdDefine;
import com.swak.define.TableDefine;
import com.swak.define.TableDefines;
import com.swak.entity.Hint;
import com.swak.entity.IdEntity;
import com.swak.entity.Page;
import com.swak.entity.Parameters;
import com.swak.exception.NoSupportException;
import com.swak.exception.StaleObjectStateException;
import com.swak.incrementer.IdGen;
import com.swak.persistence.mybatis.methods.Sqls;
import com.swak.utils.Lists;

/**
 * 数据库操作支持类
 * 
 * @Author: lifeng
 * @Date: 2020/3/28 12:56
 */
@SuppressWarnings({ "unchecked" })
public class BaseMapperImpl<T, PK> implements BaseMapper<T, PK> {

	protected volatile TableDefine<T> table;
	protected Class<?> mapper;
	protected Map<Method, MapperMethod> methodCache;

	@Autowired
	private SqlSessionTemplate sessionTemplate;

	public BaseMapperImpl() {
		this.mapper = this.getClass();
	}

	public BaseMapperImpl(SqlSessionTemplate sessionTemplate, Class<?> mapper, Boolean hasCustomMapper) {
		this.mapper = mapper;
		this.sessionTemplate = sessionTemplate;
		if (hasCustomMapper != null && hasCustomMapper) {
			this.methodCache = new ConcurrentHashMap<>();
		}
	}

	protected TableDefine<T> getTable() {
		if (this.table == null) {
			Class<?> modelClass = TableDefines.extractModelClass(this.mapper);
			TableDefine<?> table = TableDefines.lookup(modelClass);
			if (table == null) {
				throw new RuntimeException(String.format("Please define %sMapper that extends BaseMapper.",
						com.swak.utils.StringUtils.upperCaseFirstOne(this.getClass().getSimpleName())));
			}
			this.table = (TableDefine<T>) table;
		}
		return this.table;
	}

	protected Table getTableDefine() {
		return this.getTable().define;
	}

	protected String getStatementName(String operate) {
		return this.getTable().getNameSpace(operate);
	}

	protected SqlSessionTemplate getSqlRunner() {
		return this.sessionTemplate;
	}

	@Override
	public T get(PK id) {
		return (T) this.getSqlRunner().selectOne(getStatementName(Sqls.GET.getMethod()), id);
	}

	@Override
	public T getSelective(Selective qc) {
		Assert.isTrue(qc != null, "Selective can not be null");
		Assert.isTrue(qc.isSelectiveValid(), "Selective Column And Condition can not be null");
		List<T> ts = this.getSqlRunner().selectList(getStatementName(Sqls.GET_SELECTIVE.getMethod()), qc,
				new RowBounds(0, 1));
		return ts != null && ts.size() > 0 ? ts.get(0) : null;
	}

	@Override
	public T getUnique(T entity) {
		List<T> ts = this.getSqlRunner().selectList(getStatementName(Sqls.GET_UNIQUE.getMethod()), entity,
				new RowBounds(0, 1));
		return ts != null && ts.size() > 0 ? ts.get(0) : null;
	}

	@Override
	public T getByCondition(QueryCondition qc) {
		Assert.isTrue(qc != null, "Condition can not be null");
		Assert.isTrue(qc.isValid(), "Condition can not be null");
		List<T> ts = this.getSqlRunner().selectList(getStatementName(Sqls.QUERY_BY_CONDITION.getMethod()), qc,
				new RowBounds(0, 1));
		return ts != null && ts.size() > 0 ? ts.get(0) : null;
	}

	@Override
	public boolean exists(PK id) {
		Integer count = this.countByCondition(Sqls.EXISTS.getMethod(), id);
		return count != null && count == 1;
	}

	@Override
	public boolean existsByCondition(QueryCondition qc) {
		Integer count = this.countByCondition(Sqls.EXISTS_BY_CONDITION.getMethod(), qc);
		return count != null && count == 1;
	}

	@Override
	public boolean existsSelective(T entity) {
		Integer count = this.countByCondition(Sqls.EXISTS_SELECTIVE.getMethod(), entity);
		return count != null && count == 1;
	}

	@Override
	@Deprecated
	public void compareVersion(T entity) {
		PK pk = null;
		Integer myVersion = null;
		if (entity instanceof IdEntity) {
			pk = ((IdEntity<T, PK>) entity).getId();
			myVersion = ((IdEntity<T, PK>) entity).getVersion();
			myVersion = (myVersion == null ? 0 : myVersion) + 1;
			((IdEntity<T, PK>) entity).setVersion(myVersion);
		}
		Integer version = this.getSqlRunner().selectOne(Sqls.VERSION.getMethod(), pk);
		version = version == null ? 0 : version;
		if (myVersion == null || (myVersion.compareTo(version) <= 0)) {
			throw new StaleObjectStateException("数据版本过低");
		}
	}

	@Override
	public T lock(PK id) {
		T t = this.getSqlRunner().selectOne(getStatementName(Sqls.LOCK.getMethod()), id);
		if (t == null) {
			throw new StaleObjectStateException("锁异常, 锁对象不存在, id=" + id);
		}
		return t;
	}

	@Override
	public int insert(T entity) {
		return this.insert(Sqls.INSERT.getMethod(), entity);
	}

	@Override
	public int insertive(T entity) {
		if (entity instanceof IdEntity) {
			((IdEntity<T, PK>) entity).setDelAt(CurdDefine.FixDelAt);
		}
		return this.insert(Sqls.INSERTIVE.getMethod(), entity);
	}

	@Override
	public int insertive(T entity, boolean ignoreIfExists) {
		if (entity instanceof IdEntity) {
			((IdEntity<T, PK>) entity).setDelAt(CurdDefine.FixDelAt);
		}
		if (entity instanceof IdEntity && ignoreIfExists) {
			((IdEntity<T, PK>) entity).setHint(Hint.of().setIgnoreInsertFlag(true));
		}
		return this.insert(Sqls.INSERTIVE.getMethod(), entity);
	}

	@Deprecated
	@Override
	public int replace(T entity) {
		if (entity instanceof IdEntity) {
			((IdEntity<T, PK>) entity).setDelAt(CurdDefine.FixDelAt);
		}
		return this.insert(Sqls.REPLACE.getMethod(), entity);
	}

	@Deprecated
	@Override
	public int toggle(T entity) {
		return this.insert(Sqls.TOGGLE.getMethod(), entity);
	}

	@Override
	public int insert(String statementName, T entity) {
		if (entity instanceof IdEntity) {
			Table define = this.getTableDefine();
			((IdEntity<T, PK>) entity).prePersist(define.autoIncrementKey());
		}
		return this.getSqlRunner().insert(getStatementName(statementName), entity);
	}

	@Override
	public int delete(T entity) {
		if (entity instanceof IdEntity) {
			((IdEntity<T, PK>) entity).preDelete();
		}
		return this.getSqlRunner().delete(getStatementName(Sqls.DELETE.getMethod()), entity);
	}

	@Override
	public int deleteSelective(T entity) {
		if (entity instanceof IdEntity) {
			((IdEntity<T, PK>) entity).preDelete();
		}
		return this.getSqlRunner().delete(getStatementName(Sqls.DELETE_SELECTIVE.getMethod()), entity);
	}

	@Override
	public int delete(String statementName, T entity) {
		if (entity instanceof IdEntity) {
			((IdEntity<T, PK>) entity).preDelete();
		}
		return this.getSqlRunner().delete(getStatementName(statementName), entity);
	}

	@Override
	public int update(T entity) {
		return this.update(Sqls.UPDATE.getMethod(), entity);
	}

	@Override
	public int updateSelective(T entity) {
		return this.update(Sqls.UPDATE_SELECTIVE.getMethod(), entity);
	}

	@Override
	public int updative(Updative updative) {
		Assert.isTrue(updative != null, "Updative can not be null!");
		Assert.isTrue(updative.isUpdativeValid(), "Updative can not be null!");
		return this.getSqlRunner().update(getStatementName(Sqls.UPDATIVE.getMethod()), updative);
	}

	@Override
	public int update(String statementName, T entity) {
		if (entity instanceof IdEntity) {
			((IdEntity<T, PK>) entity).preUpdate();
		}
		return this.getSqlRunner().update(getStatementName(statementName), entity);
	}

	@Override
	@Deprecated
	public int clearSelective(Selective selective) {
		Assert.isTrue(selective != null, "QueryCondition can not be null");
		Assert.isTrue(selective.isSelectiveValid(), "Column And Criteria can not be null");
		return this.getSqlRunner().update(getStatementName(Sqls.CLEAR_SELECTIVE.getMethod()), selective);
	}

	@Override
	public int batchInsert(List<T> entitys) {
		return this.batchInsert(Sqls.BATCH_INSERT.getMethod(), entitys);
	}

	@Override
	public int batchInsert(String sql, List<T> entitys) {
		Table define = this.getTableDefine();
		int size = 0;
		List<T> tempList = Lists.newArrayList();
		for (T t : entitys) {
			if (t instanceof IdEntity) {
				((IdEntity<T, PK>) t).prePersist(define.autoIncrementKey());
			}
			tempList.add(t);
			if (tempList.size() == 15) {
				size += this.getSqlRunner().insert(getStatementName(sql), tempList);
				tempList.clear();
			}
		}
		if (tempList.size() > 0) {
			size += this.getSqlRunner().insert(getStatementName(sql), tempList);
		}
		return size;
	}

	@Override
	public int batchUpdate(List<T> entitys) {
		return this.batchUpdate(Sqls.UPDATE.getMethod(), entitys);
	}

	@Override
	public int batchUpdate(String statementName, List<T> entitys) {
		int size = 0;
		for (T entity : entitys) {
			if (entity instanceof IdEntity) {
				((IdEntity<T, PK>) entity).preUpdate();
			}
			size += this.getSqlRunner().update(getStatementName(statementName), entity);
		}
		return size;
	}

	@Override
	public int batchDelete(List<T> entitys) {
		int size = 0;
		for (T entity : entitys) {
			if (entity instanceof IdEntity) {
				((IdEntity<T, PK>) entity).preDelete();
			}
			size += this.getSqlRunner().delete(getStatementName(Sqls.DELETE.getMethod()), entity);
		}
		return size;
	}

	@Override
	public List<T> querySelective(T entity) {
		Assert.isTrue(entity != null, "Condition can not be null");
		return this.getSqlRunner().selectList(getStatementName(Sqls.QUERY_SELECTIVE.getMethod()), entity);
	}

	@Override
	public List<T> queryByCondition(QueryCondition qc) {
		Assert.isTrue(qc != null, "QueryCondition can not be null");
		Assert.isTrue(qc.isValid(), "Criteria can not be null");
		return this.getSqlRunner().selectList(getStatementName(Sqls.QUERY_BY_CONDITION.getMethod()), qc);
	}

	@Override
	public List<T> queryByCondition(QueryCondition qc, int size) {
		Assert.isTrue(qc != null, "QueryCondition can not be null");
		Assert.isTrue(qc.isValid(), "Criteria can not be null");
		return this.getSqlRunner().selectList(getStatementName(Sqls.QUERY_BY_CONDITION.getMethod()), qc,
				new RowBounds(0, size));
	}

	@Override
	public List<T> queryByCondition(QueryCondition qc, Parameters param) {
		Page<T> page = this.queryForPage(qc, param);
		return page.getData();
	}

	@Override
	public Integer countByCondition(QueryCondition qc) {
		Assert.isTrue(qc != null, "QueryCondition can not be null");
		Assert.isTrue(qc.isValid(), "Criteria can not be null");
		return this.getSqlRunner().selectOne(getStatementName(Sqls.COUNT_BY_CONDITION.getMethod()), qc);
	}

	@Override
	public Integer countBySelective(T entity) {
		return this.getSqlRunner().selectOne(getStatementName(Sqls.COUNT_BY_SELECTIVE.getMethod()), entity);
	}

	@Override
	public Integer countByCondition(String sqlName, Object qc) {
		return this.getSqlRunner().selectOne(getStatementName(sqlName), qc);
	}

	@Override
	public List<T> queryForList(String statementName, Object entity) {
		return this.getSqlRunner().selectList(getStatementName(statementName), entity);
	}

	@Override
	public List<T> queryForList(String statementName, Object entity, int size) {
		return this.getSqlRunner().selectList(getStatementName(statementName), entity, new RowBounds(0, size));
	}

	protected List<T> queryForList(String statementName, Object entity, RowBounds rowBounds) {
		return this.getSqlRunner().selectList(getStatementName(statementName), entity, rowBounds);
	}

	@Override
	public Page<T> queryForPage(QueryCondition qc, Parameters param) {
		Assert.isTrue(qc != null, "QueryCondition can not be null");
		Assert.isTrue(qc.isValid(), "Criteria can not be null");
		return this.queryForPage(Sqls.QUERY_BY_CONDITION.getMethod(), Sqls.COUNT_BY_CONDITION.getMethod(), qc, param);
	}

//	@Override
//	public Page<T> queryForPage(String statementName, Object qc, Parameters param) {
//		return this.queryForPage(statementName, statementName + "Stat", qc, param);
//	}

	@Override
	public Page<T> queryForPage(String statementName, String countStatementName, Object qc, Parameters param) {
		int pageNum = param.getPageIndex();
		int pageSize = param.getPageSize();
		Integer count = 0;
		List<T> lst;
		if (pageNum == Parameters.NO_PAGINATION || pageSize == Parameters.NO_PAGINATION) {
			lst = this.queryForList(statementName, qc);
		} else {
			count = this.countByCondition(countStatementName, qc);
			count = count == null ? 0 : count;
			param.setRecordCount(count);
			if (count == 0) {
				lst = Lists.newArrayList();
			} else {
				int pageCount = param.getPageCount();
				if (pageNum > pageCount) {
					pageNum = pageCount;
				}
				lst = this.queryForList(statementName, qc, new RowBounds((pageNum - 1) * pageSize, pageSize));
			}
		}
		return new Page<T>(param, lst);
	}

	@Override
	public List<T> querySelectiveByCondition(Selective qc) {
		Assert.isTrue(qc != null, "QueryCondition can not be null");
		Assert.isTrue(qc.isSelectiveValid(), "Column And Criteria can not be null");
		return this.getSqlRunner().selectList(getStatementName(Sqls.QUERY_BY_CONDITION_SELECTIVE.getMethod()), qc);
	}

	@Override
	public List<T> querySelectiveByCondition(Selective qc, int size) {
		Assert.isTrue(qc != null, "QueryCondition can not be null");
		Assert.isTrue(qc.isSelectiveValid(), "Column And Criteria can not be null");
		return this.getSqlRunner().selectList(getStatementName(Sqls.QUERY_BY_CONDITION_SELECTIVE.getMethod()), qc,
				new RowBounds(0, size));
	}

	@Override
	public Page<T> queryForSelectivePage(Selective qc, Parameters param) {
		Assert.isTrue(qc != null, "QueryCondition can not be null");
		Assert.isTrue(qc.isSelectiveValid(), "Column And Criteria can not be null");
		return this.queryForPage(Sqls.QUERY_BY_CONDITION_SELECTIVE.getMethod(), Sqls.COUNT_BY_CONDITION.getMethod(), qc,
				param);
	}

	@Override
	public <E> List<E> queryForGenericsList(String sql, Object query) {
		return this.getSqlRunner().selectList(getStatementName(sql), query);
	}

	@Override
	public <E> List<E> queryForGenericsList(String sql, Object query, int size) {
		return this.getSqlRunner().selectList(getStatementName(sql), query, new RowBounds(0, size));
	}

	@Deprecated
	@Override
	public void statistic(T entity) {
		if (!this.getTableDefine().useStat()) {
			throw new StaleObjectStateException("只有开启统计的对象才能使用统计SQL");
		}
		this.insert(Sqls.STATISTIC.getMethod(), entity);
	}

	@Override
	public int merge(T entity) {
		return this.merge(entity,
				!(entity instanceof IdEntity && !IdGen.isInvalidId(((IdEntity<T, PK>) entity).getId())));
	}

	@Override
	public int merge(T entity, boolean insert) {
		if (entity instanceof IdEntity) {
			((IdEntity<T, PK>) entity).setDelAt(CurdDefine.FixDelAt);
		}
		if (!insert) {
			if (entity instanceof IdEntity) {
				((IdEntity<T, PK>) entity).setHint(Hint.of().setMergeUpdateFlag(true));
				if (((IdEntity<T, PK>) entity).getId() == null) {
					throw new StaleObjectStateException("修改必须提供主键");
				}
			}
			return this.update(Sqls.MERGE.getMethod(), entity);
		}
		return this.insert(Sqls.MERGE.getMethod(), entity);
	}

	/**
	 * 通用的执行方法，应对自定义的方法
	 * 
	 * @param mapper
	 * @param method
	 * @return
	 */
	protected Object execute(Method method, Object[] args) {
		if (this.methodCache == null) {
			throw new NoSupportException("不支持此功能！");
		}
		return this.cachedInvoker(method).execute(this.sessionTemplate, args);
	}

	private MapperMethod cachedInvoker(Method method) {
		MapperMethod invoker = this.methodCache.get(method);
		if (invoker != null) {
			return invoker;
		}
		return methodCache.computeIfAbsent(method, m -> {
			return new MapperMethod(this.mapper, method, this.sessionTemplate.getConfiguration());
		});
	}

}